#include "ChunckManager.h"

int ChunckManager::NUM_VOXEL_CHUNCKS = 4;
int ChunckManager::NUM_PUDDLE_CHUNCKS = 5;

vector<Vector3> ChunckManager::POSITIONS_PUDDLES = { Vector3(0, 100, 0), Vector3(500, 0, 0), Vector3(200, 0, 200) };

ChunckManager::ChunckManager(float scale, bool multiplayer, TerrainGenerationData* tGData) : scaleTerrain(scale){
	if (multiplayer) {
		NUM_VOXEL_CHUNCKS = tGData->getNumVoxelChunks();
		NUM_PUDDLE_CHUNCKS = tGData->getNumTrees();
		xOffset = tGData->getOffsetInNoiseMap().x;
		zOffset = tGData->getOffsetInNoiseMap().z;
	}
	else {
		//random terrain
		xOffset = rand() % 100;
		zOffset = rand() % 500;
	}

	//perlinModule.SetFrequency(5.0);

	//createHeightMapForLandscape();
	texture = SOIL_load_OGL_texture(TEXTUREDIR"texture.png", SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_MIPMAPS);
	bumpTexture = SOIL_load_OGL_texture(TEXTUREDIR"GRASS_NORMAL.jpg", SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_MIPMAPS);

	for (int i = 0; i < NUM_PUDDLE_CHUNCKS; i = i + 2) {
		puddleTextures.push_back(SOIL_load_OGL_texture(TEXTUREDIR"tree_tex_01.png", SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_MIPMAPS));
		puddleTextures.push_back(SOIL_load_OGL_texture(TEXTUREDIR"tree_tex_02.png", SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_MIPMAPS));
	}

	Vector3 start(0, 0, 0);

	int i;
	for (i = 0; i < NUM_VOXEL_CHUNCKS; i++) {
		VoxelChunck* vChunck;
		if (multiplayer) {
			vChunck = new VoxelChunck(texture, bumpTexture, start, i, xOffset, zOffset, perlinModule, multiplayer, tGData);
		}
		else {
			vChunck = new VoxelChunck(texture, bumpTexture, start, i, xOffset, zOffset, perlinModule);
		}
		//set the starting point for the next chunck of terrain
		start = vChunck->getEndOffset();
		voxelChuncks.push_back(vChunck);

		//update the coordinates for the heightmap
		xOffset += VoxelChunck::getXChunckSize();
		zOffset += VoxelChunck::getZChunckSize();

	}
	positionToPlaceAnotherChunck = start;
	if (multiplayer) {
		pendingChunck = new VoxelChunck(texture, bumpTexture, start, NUM_VOXEL_CHUNCKS, xOffset, zOffset, perlinModule, multiplayer, tGData);
	}
	else {
		pendingChunck = new VoxelChunck(texture, bumpTexture, start, NUM_VOXEL_CHUNCKS, xOffset, zOffset, perlinModule);
	}
	start = pendingChunck->getEndOffset();
	positionToPlaceAnotherChunck = start;

	//update the coordinates for the heightmap
	xOffset += VoxelChunck::getXChunckSize();
	zOffset += VoxelChunck::getZChunckSize();
	POSITION_LAST_CHUNCK = Vector3(VoxelChunck::getXChunckSize() / 2, 0, voxelChuncks.at(NUM_VOXEL_CHUNCKS - 2)->getEndOffset().z);

	//creation of voxel trees

	//initialize the puddle Sizes and positions
	for (int i = 0; i < NUM_PUDDLE_CHUNCKS; i++) {
		if (multiplayer) {
			for (int i = 0; i < tGData->getNumTrees(); i++) {
				int sz = tGData->getTreeSizesAt(i);
				puddleSizes.push_back(Vector3(sz, sz, sz));
				puddleStartPositions.push_back(tGData->getTreePositionAt(i));
			}
		}
		else {
			int randNum = rand() % 10 + 10;				//bigger trees
			puddleSizes.push_back(Vector3(randNum, randNum, randNum));
			puddleStartPositions.push_back(getPositionOfOnePuddle(i));
		}

		//initialize the puddles
		puddleChuncks.push_back(new PuddleChunck(puddleTextures.at(i), puddleTextures.at(i), puddleStartPositions.at(i), i, puddleSizes.at(i).x, puddleSizes.at(i).y, puddleSizes.at(i).z));
	}
}

Vector3 ChunckManager::getPositionOfOnePuddle(int i) {
	int x = rand() % 100;
	if (x > 50) {
		x = VoxelChunck::getXChunckSize() - 6;
	}
	else {
		x = -9;
	}


	int y = 26.0f;
	int z = (rand() % (int)puddleSizes.at(i).z) + (rand() % 200);

	return Vector3(x * scaleTerrain, y, z * scaleTerrain);
}

void ChunckManager::setPositionOfPuddleChuncks() {
	for (int i = 0; i < NUM_PUDDLE_CHUNCKS; i++) {
		puddleStartPositions.at(i) = getPositionOfOnePuddle(i);
	}
}

ChunckManager::~ChunckManager() {
	for (int i = 0; i < voxelChuncks.size(); i++) {
		delete voxelChuncks.at(i);
	}
	voxelChuncks.clear();
}

void ChunckManager::createChunck() {
	////the next chunck of terrain will be created at the end of the existing piece of terrain
	std::rotate(voxelChuncks.begin(), voxelChuncks.begin() + 1, voxelChuncks.end());

	//update the last voxel in the terrain to reflect the new terrain for the player to go on
	int index = voxelChuncks.size() - 1;
	voxelChuncks.at(index)->updateChunck(texture, bumpTexture, positionToPlaceAnotherChunck, xOffset, zOffset, perlinModule);

	////in case the offset gets too big
	//if ((xOffset / VoxelChunck::getXChunckSize()) > 1000) {
	//	xOffset = 0.0f;
	//}
	//if ((zOffset / VoxelChunck::getZChunckSize()) > 1000) {
	//	zOffset = 0.0f;
	//}

	//update the coordinates for the heightmap
	xOffset += VoxelChunck::getXChunckSize();
	zOffset += VoxelChunck::getZChunckSize();

	//set the starting point for the next chunck of terrain
	positionToPlaceAnotherChunck = voxelChuncks.at(voxelChuncks.size() - 1)->getEndOffset();
}

void ChunckManager::addChunkThreaded()
{
	//current chunck that need to be poped out.
	VoxelChunck* currentChunck = voxelChuncks[0];
	voxelChuncks.erase(voxelChuncks.begin());
	//after poping the chunck out, push the chunck at the front of the pending list.
	pendingChunkMutex.lock();
	pendingChunck->updateMesh();
	voxelChuncks.push_back(pendingChunck);
	pendingChunck = currentChunck;
	pendingChunkMutex.unlock();
	//then modify the poped chunck
	ModifyChunckThreaded(pendingChunck);
}

void ChunckManager::ModifyChunckThreaded(VoxelChunck* currentChunck)
{
	positionToPlaceAnotherChunck = voxelChuncks.at(voxelChuncks.size() - 1)->getEndOffset();

	//update the position of the spawner object
	POSITION_LAST_CHUNCK = Vector3(VoxelChunck::getXChunckSize() / 2, 0, voxelChuncks.at(voxelChuncks.size() - 2)->getEndOffset().z);

	auto modifyChunck = [&](){
		pendingChunkMutex.lock();
		pendingChunck->updateChunckThreaded(texture, bumpTexture, positionToPlaceAnotherChunck, xOffset, zOffset, perlinModule);
		pendingChunkMutex.unlock();
		cout << "thread finished" << endl;
	};
	std::thread modifyChunckThread(modifyChunck);
	modifyChunckThread.detach();

	//update the coordinates for the heightmap
	xOffset += VoxelChunck::getXChunckSize();
	zOffset += VoxelChunck::getZChunckSize();
}

void ChunckManager::restartChunckManager() {
	xOffset = 0.0f;
	zOffset = 0.0f;
	Vector3 start(0, 0, 0);

	for (int i = 0; i < voxelChuncks.size(); i++) {
		voxelChuncks.at(i)->updateChunck(texture, bumpTexture, start, xOffset, zOffset, perlinModule);

		start = voxelChuncks.at(i)->getEndOffset();

		//update the coordinates for the heightmap
		xOffset += VoxelChunck::getXChunckSize();
		zOffset += VoxelChunck::getZChunckSize();
	}

	positionToPlaceAnotherChunck = start;
	pendingChunck->updateChunck(texture, bumpTexture, start, xOffset, zOffset, perlinModule);
	start = pendingChunck->getEndOffset();
	positionToPlaceAnotherChunck = start;

	//update the coordinates for the heightmap
	xOffset += VoxelChunck::getXChunckSize();
	zOffset += VoxelChunck::getZChunckSize();
	POSITION_LAST_CHUNCK = Vector3(VoxelChunck::getXChunckSize() / 2, 0, voxelChuncks.at(NUM_VOXEL_CHUNCKS - 2)->getEndOffset().z);
}

Mesh* ChunckManager::getMeshAt(int i) {
	return voxelChuncks.at(i)->getCube();
}

VoxelChunck* ChunckManager::getChunckAt(int i) {
	return voxelChuncks.at(i);
}

Mesh* ChunckManager::getMeshWithId(int id) {
	for (int i = 0; i < voxelChuncks.size(); i++) {
		if (voxelChuncks.at(i)->getId() == id) {
			return voxelChuncks.at(i)->getCube();
		}
	}
}

bool ChunckManager::getVoxelActive(Vector3 position) const {
	return voxelChuncks.at(0)->getIsVoxelActive(position);
}

float ChunckManager::getHeightAtPosition(Vector3 position, float scaleTerrain) {
	//transform the start position of the entity into voxel coordinates
	Vector3 voxels = transformWorldCoordinatesToVoxelCoordinates(position, scaleTerrain, 0, 0, 0);
	return voxelChuncks.at(voxelChuncks.size() - 1)->getHeightAtPosition(voxels, perlinModule, scaleTerrain);
}

Vector3 ChunckManager::transformVoxelCoordinatesToWorldCoordinates(Vector3 position, float scale, int xOffset = 0, int yOffset = 0, int zOffset = 0) {
	float x = position.x * scale + xOffset;
	float y = (position.y * scale) + yOffset;
	float z = (position.z * scale) + zOffset;

	return Vector3(x, y, z);
}

Vector3 ChunckManager::transformWorldCoordinatesToVoxelCoordinates(Vector3 position, float scale, int xOffset = 0, int yOffset = 0, int zOffset = 0) {
	int x = round((position.x + xOffset) / scale);
	int y = round((position.y + yOffset) / scale);
	int z = round(abs(position.z + zOffset) / scale);

	return Vector3(x, y, z);
}

int ChunckManager::getMaxHeightTerrain() const {
	int max = voxelChuncks.at(0)->getMaxHeightTerrain();
	for (int i = 1; i < voxelChuncks.size(); i++) {
		if (max < voxelChuncks.at(i)->getMaxHeightTerrain()) {
			max = voxelChuncks.at(i)->getMaxHeightTerrain();
		}
	}
	return max;
}

Mesh* ChunckManager::getPuddleChunckAt(int id) {
	return puddleChuncks.at(id)->getCube();
}